// 
//  Copyright © 2009 Jiří Zárevúcky <zarevucky.jiri@gmail.com>
// 
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as
//  published by the Free Software Foundation, either version 3 of the
//  License, or (at your option) any later version.
// 
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.
// 
//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// 

using System;
using System.Threading;
using System.Runtime.CompilerServices;
using Galaxium.Protocol.Xmpp.Library;
using Galaxium.Protocol.Xmpp.Library.Xml;
using Galaxium.Protocol.Xmpp.Library.Sasl;
using Galaxium.Protocol.Xmpp.Library.Utility;

namespace Galaxium.Protocol.Xmpp.Library.Core
{
	internal class Authenticator
	{
		private const int SASL_TIMEOUT = 10000;
		
		private CoreStream _stream;
		private Func<string> _invalid_password_handler;
		private Func<Element, Element> _send_method;
		
		public Authenticator (CoreStream stream,
		                      Func<string> invalid_password_handler,
		                      Func<Element, Element> send_method)
		{
			if (stream == null)
				throw new ArgumentNullException ("stream");
			if (invalid_password_handler == null)
				throw new ArgumentNullException ("invalid_password_handler");
			if (send_method == null)
				throw new ArgumentNullException ("send_method");
			_stream = stream;
			_invalid_password_handler = invalid_password_handler;
			_send_method = send_method;
		}

		private Mechanism GetAuthMechanism ()
		{
			if (_stream.Features.SupportsSASLMechanism ("DIGEST-MD5"))
				return new Sasl.DigestMD5 (_send_method);
			else if (_stream.Features.SupportsSASLMechanism ("PLAIN"))
				return new Sasl.Plain (_send_method);
			throw new LoginException ("Server doesn't allow any supported authentication mechanism.");
		}
		
		public void Authenticate (JabberID jid, string password)
		{
			Mechanism mechanism = GetAuthMechanism ();
			
			while (password != null) {
				try {
					mechanism.Auth (jid, password);
					break;
				}
				catch (SaslError e) {
					if (e.ErrorCondition == "not-authorized")
						password = _invalid_password_handler ();
					else throw;
				}
			}
			
			if (password == null)
				throw new LoginCancelledException ();
		}
	}
}
